import {
    addIcon, App, HeadingCache, MarkdownView, normalizePath, Notice,
    Plugin, PluginSettingTab, Setting, TFile, TFolder, Vault
} from "obsidian";
import * as Path from "path";
import * as fs from 'fs';
import {exec, ExecException, execFile, spawn, spawnSync} from "child_process";

class HugoProject {
    sourceDir: string = "";
    contentDir: string = "";
    outputDir: string = "";
    enabled: boolean = true;
    // constructor();
    constructor(sourceDir?: string, contentDir?: string, outputDir?: string, enabled?: boolean) {
        this.sourceDir = sourceDir;
        this.contentDir = contentDir;
        this.outputDir = outputDir;
        this.enabled = enabled;
    }
}

interface PluginSettings {
    hugoRootDir: string,
    hugoPostRelativeDir: string,
    hugoStaticRelativeDir: string,

    hugoExecutablePath: string,
    hugoProjects: Array<HugoProject>,
    selectedHugoProject: number,
}

const DEFAULT_SETTINGS: PluginSettings = {
    hugoRootDir: "D:/projects/kp-blog/myblog",
    hugoPostRelativeDir: "content/post",
    hugoStaticRelativeDir: "static",

    hugoExecutablePath: "D:/projects/kp-blog/hugo.exe",
    hugoProjects: [
        new HugoProject("d:/projects/kp-blog/myblog",
            "d:/projects/kp-blog/myblog/content",
            "d:/projects/kp-blog/myblog/public",
            true)
    ],
    selectedHugoProject: 0,
}

//Add chevron-up-square icon from lucide for mobile toolbar (temporary until Obsidian updates to Lucide v0.130.0)
addIcon("chevron-up-square", `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-chevron-up-square"><rect width="18" height="18" x="3" y="3" rx="2" ry="2"></rect><polyline points="8,14 12,10 16,14"></polyline></svg>`);

export default class ExportToHugoPlugin extends Plugin {
    settings: PluginSettings

    async onload() {
        await this.loadSettings();

        this.addCommand({
            id: "export-to-hugo",
            name: "Export current note to hugo.",
            icon: "plus-square",
            callback: () => {
                this.exportToHugo();
            },
        });

        this.addCommand({
            id: "start-hugo-server",
            name: "Start hugo server.",
            icon: "plus-square",
            callback: () => {
                // this.exportToHugo();
                // const cmd: string = normalizePath("D:/projects/kp-blog/hugo.exe");
                const cmd: string = normalizePath(this.settings.hugoExecutablePath.trim());
                const args: Array<string> = [
                    "server",
                    `--source=${normalizePath(this.settings.hugoProjects[this.settings.selectedHugoProject].sourceDir.trim())}`,
                    `--contentDir=${normalizePath(this.settings.hugoProjects[this.settings.selectedHugoProject].contentDir.trim())}`,
                    `--destination=${normalizePath(this.settings.hugoProjects[this.settings.selectedHugoProject].outputDir.trim())}`,
                ];
                this.startNewProcess(cmd, args);
                // this.startChildProcess(cmd, args);
            },
        });

        this.addCommand({
            id: "build-hugo-static",
            name: "Build hugo static pages.",
            icon: "plus-square",
            callback: () => {
                // this.exportToHugo();
                // const cmd: string = "start D:/projects/kp-blog/hugo.exe --source='d:/projects/kp-blog/myblog' --contentDir='d:/projects/kp-blog/myblog/content' --destination='d:/projects/kp-blog/myblog/public' -v";
                const cmd: string = normalizePath(this.settings.hugoExecutablePath.trim());
                const args: Array<string> = [
                    `--source=${normalizePath(this.settings.hugoProjects[this.settings.selectedHugoProject].sourceDir.trim())}`,
                    `--contentDir=${normalizePath(this.settings.hugoProjects[this.settings.selectedHugoProject].contentDir.trim())}`,
                    `--destination=${normalizePath(this.settings.hugoProjects[this.settings.selectedHugoProject].outputDir.trim())}`,
                ];

                this.executeProcess(cmd, args);
            },
        });

        this.addCommand({
            id: "push-hugo-static",
            name: "Push hugo static pages.",
            icon: "plus-square",
            callback: () => {
                // this.exportToHugo();
                // const cmd: string = "start D:/projects/kp-blog/hugo.exe --source='d:/projects/kp-blog/myblog' --contentDir='d:/projects/kp-blog/myblog/content' --destination='d:/projects/kp-blog/myblog/public' -v";
                const cmd: string = "git";
                const args: Array<string> = [
                    `-C`,
                    normalizePath(this.settings.hugoProjects[this.settings.selectedHugoProject].outputDir),
                    `push`,
                    `origin`,
                    `master`,
                ];

                this.executeProcess(cmd, args);
            },
        });
        // add settings tab
        this.addSettingTab(new SettingTab(this.app, this));
    }
    // async readActiveFile() {
    //     const currentNote = this.app.workspace.getActiveFile();
    //     if(!currentNote) return;
    //     return await this.app.vault.read(currentNote);
    // }
    transformObsidianLink(link: string, callback: (title: string) => string) {
        if(link.includes('|')) {
            const p = link.split('|');
            return `[${p[1]}](${callback(p[0])})`
        } else {
            return `[${link}](${callback(link)})`
        }
    }

    processInternalLinks(content: string, callback: (link: string) => string) {
        // JM：将[[xxx]]这种obsidian特有的link，转换成[xxx](url)这种markdown标准格式
        return content.replace(/\[\[(.*?)\]\]/g, (match, p1) => callback(p1));
    }
    // slugify(text: string) {
    //     return text.toLowerCase().replace(/ /g, '-').replace(/[^\w-]+/g, '').replace(/-+/, '-');
    // }

    appendHugoMetadata(content: string, metadata: { [key: string]: string | Array<string> | boolean | Date }) {
        let metadataText = "";
        const props = Object.keys(metadata);
        for(let i = 0; i < props.length; i++) {
            let metadataValue = metadata[props[i]];
            if (metadataValue instanceof Array){
                const tempArray = metadataValue.map((item) => {
                    return `'${item}'`;
                });
                metadataValue = `[${tempArray.join(",")}]`;
            } else if (typeof metadataValue === 'string') {
                metadataValue = `'${metadataValue}'`;
            } else if (metadataValue instanceof Date) {
                metadataValue = metadataValue.toISOString();
            } else {
                metadataValue = metadata[props[i]].toString();
            }
            metadataText += `${props[i]} = ${metadataValue}\n`
        }
        return `+++\n${metadataText}+++\n\n${content}`;
    }

    travel(dir:string ,callback: Function){
        fs.readdirSync(dir).forEach((file)=>{
            var pathname=Path.join(dir,file)
            if(fs.statSync(pathname).isDirectory()){
                this.travel(pathname,callback)
            }else{
                callback(pathname)
            }
        })
    }
    // travel('F:/HTML/Node/test',function(pathname){
    //     console.log(pathname)
    // })
    copyRecursiveSync (src: string, dest: string) {
        const exists = fs.existsSync(src);
        const stats = exists && fs.statSync(src);
        const isSrcDirectory = exists && stats.isDirectory();
        if (isSrcDirectory) {
            if (!fs.existsSync(dest)) {
                fs.mkdirSync(dest);
            }
            fs.readdirSync(src).forEach((childItemName) => {
                this.copyRecursiveSync(Path.join(src, childItemName), Path.join(dest, childItemName));
            });
        } else {
            // images(src).saveAsync(dest, {
            //     quality: 50
            // });
            fs.copyFileSync(src, dest);
        }
    };

    async startNewProcess(cmd: string, args: Array<string>) {
        exec(`start ${cmd} ${args.join(" ")}`);
    }
    async executeProcess(cmd: string, args: Array<string>) {
        const command = execFile(cmd, args, (error: ExecException, stdout: string, stderr: string) => {
            console.log({ error, stdout, stderr });
            if (error) {
                new Notice(error.message);
            } else {
                if (stdout) new Notice(`info: ${stdout}`);
                if (stderr) new Notice(`error: ${stderr}`);
            }
        });

        // if (command.error) {
        //     console.error(command.error);
        //     return false;
        // } else {
        //     // @ts-ignore
        //     const output = new TextDecoder().decode(command.stdout);
        //     new Notice(output);
        //     return true;
        // }
        // const result = spawn('D:/projects/kp-blog/hugo.exe', ["--source='d:/projects/kp-blog/myblog'", "--contentDir='d:/projects/kp-blog/myblog/content'", "--destination='d:/projects/kp-blog/myblog/public'", "-v"]);
        // const execFile = require('child_process').execFile;
        // execFile(normalizePath("hugo.exe"), ['--source="d:\\projects\\kp-blog\\myblog"', '--contentDir="d:\\projects\\kp-blog\\myblog\\content"', '--destination="d:\\projects\\kp-blog\\myblog\\public"'], (error: ExecException, stdout: string, stderr: string) => {
        //                 console.log({ error, stdout, stderr });
        //             });
        // switch (process.platform) {
        //     case "darwin":
        //         exec("open");
        //         break;
        //     case "win32":
        //         exec(cmd, (error: ExecException, stdout: string, stderr: string) => {
        //             console.log({ error, stdout, stderr });
        //         });
        //         break;
        //     default:
        //         exec("xdg-open");
        // }
    }

    async exportToHugo() {
        const activeFile = await this.app.workspace.getActiveFile();
        if(!activeFile) return;

        const frontmatter = this.app.metadataCache.getCache(activeFile.path).frontmatter;
        const date = new Date(frontmatter["date"]);
        let title;
        if (frontmatter["id"].indexOf("_") !== -1) {
            title = frontmatter["id"].split("_").slice(0, -1).join("_");
        } else {
            title = frontmatter["id"];
        }
        const tags = frontmatter["tags"];
        const categories = frontmatter["categories"];
        const series = frontmatter["series"];
        let draft = false;

        const text = await this.app.vault.read(activeFile) || '';
        // const text = await this.readActiveFile() || '';
        const hugoRootDir = this.settings.hugoRootDir;
        const hugoPostDir = Path.join(hugoRootDir, this.settings.hugoPostRelativeDir);
        const hugoStaticDir = Path.join(hugoRootDir, this.settings.hugoStaticRelativeDir);

        // const hugoExportDir = "D:/projects/kp-blog/myblog/content/post";
        // const imageFolderPath = hugoExportDir.split('/').last()
        const imageFolderPath = activeFile.basename;
        let updatedContent = this.processInternalLinks(text, (link) => {
            // return this.transformObsidianLink(link, (title) => `/${imageFolderPath}/${this.slugify(title)}`)
            return this.transformObsidianLink(link, (title) => `/${imageFolderPath}/${title}`);
        });

        //@ts-ignore
        let attachmentFolderPath = this.app.vault.getConfig('attachmentFolderPath'); // e.g. ./attachments/本地游戏库管理工具，Playnite插件LocalGameLibrary
        attachmentFolderPath = Path.join(attachmentFolderPath, "../");
        //@ts-ignore
        const attachmentFolderAbsoluteBasePath = Path.dirname(normalizePath(Path.join(this.app.vault.adapter.basePath, activeFile.path))) // e.g. D:/ObsidianVault/Personal/3_Pages
        const attachmentFolderAbsolutePath = Path.join(attachmentFolderAbsoluteBasePath, attachmentFolderPath); // e.g. D:/ObsidianVault/Personal/3_Pages/attachments
        this.copyRecursiveSync(normalizePath(Path.join(attachmentFolderAbsolutePath, activeFile.basename)), normalizePath(Path.join(hugoStaticDir, Path.basename(activeFile.basename))));
        // this.travel(normalizePath(attachmentFolderAbsolutePath), (pathname: string) => {
        //     // JM: 将pathname翻译成绝对路径，目前是当前路径"./attachment..."
        //     this.copyRecursiveSync(normalizePath(Path.dirname(pathname)), normalizePath(Path.join(hugoStaticDir, Path.basename(pathname))));
        //     // fs.copyFileSync(normalizePath(pathname), normalizePath(Path.join(hugoStaticDir, Path.basename(pathname))));
        // });
        console.log(attachmentFolderPath);
        const hugoMetadata = {
            "categories": categories,
            "tags": tags,
            "series": series,
            "title": title,
            "date":  date,
            "draft": draft,
        }

        const frontmatterPosition = this.app.metadataCache.getCache(activeFile.path).frontmatterPosition;
        const entireFrontMatterText = updatedContent.substring(frontmatterPosition.start.offset, frontmatterPosition.end.offset);
        updatedContent = updatedContent.replace(entireFrontMatterText, "").trim();
        updatedContent = this.appendHugoMetadata(updatedContent, hugoMetadata);

        fs.writeFileSync(`${hugoPostDir}/${activeFile.basename}.md`, updatedContent);
        new Notice(`export success! written to ${hugoPostDir}/${activeFile.basename}.md`);
    }
    async loadSettings() {
        this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
    }

    async saveSettings() {
        await this.saveData(this.settings);
    }
}

class SettingTab extends PluginSettingTab {
    plugin: ExportToHugoPlugin;

    constructor(app: App, plugin: ExportToHugoPlugin) {
        super(app, plugin);
        this.plugin = plugin;
    }

    display(): void {
        const {containerEl} = this;
        containerEl.empty();

        /* Single Jobs Setting */
        this.containerEl.createEl("h2", { text: "General setting" });
        const desc = document.createDocumentFragment();
        desc.append(
            "Define Hugo Post and Static resources output path."
        );
        new Setting(this.containerEl).setDesc(desc);

        new Setting(containerEl)
            .setName('Hugo Root Path')
            .setDesc('the root path for hugo.')
            .addText(text => text
                .setPlaceholder("e.g. D:/projects/kp-blog/myblog")
                .setValue(this.plugin.settings.hugoRootDir)
                .onChange(async (value) => {
                        this.plugin.settings.hugoRootDir = value;
                        await this.plugin.saveSettings();
                    }
                ));
        new Setting(containerEl)
            .setName('Hugo Post Relative Path')
            .setDesc('the post relative path for hugo.')
            .addText(text => text
                .setPlaceholder("e.g. content/post")
                .setValue(this.plugin.settings.hugoPostRelativeDir)
                .onChange(async (value) => {
                        this.plugin.settings.hugoPostRelativeDir = value;
                        await this.plugin.saveSettings();
                    }
                ));
        new Setting(containerEl)
            .setName('Hugo Static Resource Relative Path')
            .setDesc('the static resource relative path for hugo.')
            .addText(text => text
                .setPlaceholder("e.g. static")
                .setValue(this.plugin.settings.hugoStaticRelativeDir)
                .onChange(async (value) => {
                        this.plugin.settings.hugoStaticRelativeDir = value;
                        await this.plugin.saveSettings();
                    }
                ));

        this.containerEl.createEl("h2", { text: "Define Hugo Projects" });
        const desc2 = document.createDocumentFragment();
        desc2.append(
            "Define Hugo Projects by SourceDir, ContentDir, OutputDir and toogle enabled."
        );
        new Setting(this.containerEl).setDesc(desc2);
        this.plugin.settings.hugoProjects.forEach((hugoProject, index) => {
            const s = new Setting(this.containerEl)
                .addText(text => text
                    .setPlaceholder("d:/projects/kp-blog/myblog")
                    .setValue(this.plugin.settings.hugoProjects[index].sourceDir)
                    .onChange(async (value) => {
                            this.plugin.settings.hugoProjects[index].sourceDir = value;
                            await this.plugin.saveSettings();
                        }
                    ))
                .addText(text => text
                    .setPlaceholder("d:/projects/kp-blog/myblog/content")
                    .setValue(this.plugin.settings.hugoProjects[index].contentDir)
                    .onChange(async (value) => {
                            this.plugin.settings.hugoProjects[index].contentDir = value;
                            await this.plugin.saveSettings();
                        }
                    ))
                .addText(text => text
                    .setPlaceholder("d:/projects/kp-blog/myblog/public")
                    .setValue(this.plugin.settings.hugoProjects[index].outputDir)
                    .onChange(async (value) => {
                            this.plugin.settings.hugoProjects[index].outputDir = value;
                            await this.plugin.saveSettings();
                        }
                    ))
                .addToggle(toogle => toogle
                    .setValue(this.plugin.settings.hugoProjects[index].enabled)
                    .onChange(async (value) => {
                            this.plugin.settings.hugoProjects[index].enabled = value;
                            await this.plugin.saveSettings();
                        }
                    ))
                .addExtraButton((cb) => {
                    cb.setIcon("cross")
                        .setTooltip("Delete")
                        .onClick(() => {
                            this.plugin.settings.hugoProjects.splice(
                                index,
                                1
                            );
                            this.plugin.saveSettings();
                            // Force refresh
                            this.display();
                        });
                })
                .addExtraButton(cb => {
                    cb.setIcon("copy")
                        .setTooltip("Copy")
                        .onClick(() =>{
                            let copiedHugoProject = new HugoProject(
                                this.plugin.settings.hugoProjects[index].sourceDir,
                                this.plugin.settings.hugoProjects[index].contentDir,
                                this.plugin.settings.hugoProjects[index].outputDir,
                                true
                            );
                            this.plugin.settings.hugoProjects.push(copiedHugoProject);
                            this.plugin.saveSettings();
                            // Force refresh
                            this.display();
                        })
                })
        });
        new Setting(containerEl)
            .setName('Selected index of Hugo projects')
            .setDesc('selected index of Hugo projects (starting from index 0).')
            .addText(number => number
                .setPlaceholder("e.g. 0")
                .setValue(this.plugin.settings.selectedHugoProject.toString())
                .onChange(async (value) => {
                        this.plugin.settings.selectedHugoProject = Number(value);
                        await this.plugin.saveSettings();
                    }
                ));
        new Setting(this.containerEl).addButton((cb) => {
            cb.setButtonText("Add new Hugo Project")
                .setCta()
                .onClick(() => {
                    this.plugin.settings.hugoProjects.push(new HugoProject());
                    this.plugin.saveSettings();
                    // Force refresh
                    this.display();
                });
        });
    }
}
